""" -*- mode: python; coding: utf-8; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
"""

import sys, python_config, pyintromit, unittest, python_runner
pyintromit.load("intromit/test/.libs/libpython_obj.so")
import libpython_obj


class test_5_obj(unittest.TestCase):

    def assertExpression(self, exception, expression):
        try:
            exec(expression)
            self.fail("Expression did not failed: "+expression)
        except exception:
            pass

    def test_01(self):
        "Reference counted objects: just created"
        self.assertEqual(libpython_obj.reference_class().get_refcnt(), 1)
        self.assertEqual(libpython_obj.sample_class().get_refcnt(), 1)

    def test_02(self):
        "Reference counted objects: multiple references"
        sc=libpython_obj.sample_class()
        sc.show_dtor_refcnt_error()               # check refcnt in destructor
        self.assertEqual(sc.get_refcnt(), 1)

        # python refcnt
        sc_ref1=sc
        self.assertEqual(sc.get_refcnt(), 1)      # C/C++ reference count still 1 because
        self.assertEqual(sc_ref1.get_refcnt(), 1) # python refcnt are the increased one

        # C/C++ refcnt
        cc=libpython_obj.container_class()
        cc.sample_objp=sc
        self.assertEqual(sc.get_refcnt(), 2)      # now C/C++ object has 2 references

        # check same python obj
        self.assertEqual(cc.sample_objp, sc)

        # del python reference
        del sc, sc_ref1
        self.assertEqual(cc.get_sample_objp_refcnt(), 1)

        # create python reference and del container one
        sc=cc.sample_objp
        cc.sample_objp=None
        self.assertEqual(sc.get_refcnt(), 1)

    def test_03(self):
        "Pass objects pointers to methods"
        cc=libpython_obj.container_class()

        # reference counted obj
        sc=libpython_obj.sample_class()
        cc.pass_objp(sc)
        self.assertEqual(sc.get_refcnt(), 1)

        # non-reference counted obj
        ss=libpython_obj.sample_struct()
        cc.pass_objp(ss)

    def test_04(self):
        "Return object pointers by methods"
        cc=libpython_obj.container_class()
        self.assertEqual(cc.sample_objp, None)
        self.assertEqual(cc.sample_str_objp, None)
        self.assertEqual(cc.get_sample_class(), None)
        self.assertEqual(cc.get_sample_struct1(), None)

        # now set objects
        sc=libpython_obj.sample_class()
        ss=libpython_obj.sample_struct()
        cc.sample_objp=sc
        cc.sample_str_objp=ss
        self.assert_(sc)
        self.assert_(ss)
        self.assert_(cc)

        # check again
        self.assertEqual(cc.get_sample_class(), sc)
        #! self.assertEqual(cc.get_sample_struct1(), ss)
        self.assertEqual(cc.get_sample_struct2(), cc.sample_str_obj)

    def test_05(self):
        "Python to C++ assignments"

        # intensive python_metadata - C++ interoperativity
        cc=libpython_obj.container_class()
        cc.new_sample_objp()
        cc.new_sample_str_objp()
        p1=libpython_obj.sample_class()  # P1 one ref
        q1=cc.sample_objp                # P1 two ref
        p2=libpython_obj.sample_struct() # P2
        p3=cc.sample_obj                 # P3 ref
        q3=cc.sample_str_obj             # P3 no ref
        p4=cc.sample_str_objp            # P4

        # check objects created
        self.assert_(cc, "CC")
        self.assert_(p1)
        self.assert_(q1)
        self.assert_(p2)
        self.assert_(p3)
        self.assert_(q3)
        self.assert_(p4)

        # check internal info
        self.assertEqual(p1.__debug_internal__(), (1, 0,  1, 0), "P1 one")
        self.assertEqual(q1.__debug_internal__(), (1, 0,  2, 0), "P1 two")
        self.assertEqual(p2.__debug_internal__(), (1, 0, -1, 1), "P2")
        self.assertEqual(p3.__debug_internal__(), (1, 1, -1, 2), "P3 ref")
        self.assertEqual(q3.__debug_internal__(), (1, 1, -1, 2), "P3 no ref")
        self.assertEqual(p4.__debug_internal__(), (1, 1, -1, 3), "P4")

        # P1 -> C1A1
        cc.sample_objp=p1
        self.assertEqual(p1.__debug_internal__(), (1, 0,  2, 0), "P1 one -> two")
        self.assertEqual(q1.__debug_internal__(), (1, 0,  1, 0), "P1 two -> one")
        cc.sample_objp=q1
        self.assertEqual(p1.__debug_internal__(), (1, 0,  1, 0), "P1 one -> two -> one")
        self.assertEqual(q1.__debug_internal__(), (1, 0,  2, 0), "P1 two -> one -> two")

        # P1 -> C1A2
        cc.sample_obj=p1
        self.assertEqual(p1.__debug_internal__(), (1, 1, -1, 2), "P1 one -> P3")
        cc.sample_obj=libpython_obj.sample_class()
        self.assertEqual(p1.__debug_internal__(), (1, 0,  1, 0), "P3 -> P1 one: "+str(p1)+":"+str(p1.__debug_internal__()))

        # P2 -> C2A2
        cc.sample_str_obj=p2
        self.assertEqual(p2.__debug_internal__(), (1, 1, -1, 2), "P2 -> P3")
        cc.sample_str_obj=libpython_obj.sample_struct()
        self.assertEqual(p2.__debug_internal__(), (1, 0, -1, 1), "P3 -> P2")

        # P2 -> C2A1+
        cc.sample_str_objp=p2

        # P3 -> C1A2
        cc2=libpython_obj.container_class()
        cc2.sample_obj.payload=777
        r3=cc.sample_obj
        r3.payload=555
        cc.sample_obj=cc2.sample_obj   # <- this is the conversion line
        self.assertEqual(r3.payload, 555)
        self.assertEqual(cc.sample_obj.payload, 777)

    def test_06(self):
        "P3 embedding system"
        e1=libpython_obj.sample_embed1()
        e3=e1.emb.emb
        e4=e3.emb
        e5=e4.emb
        del e3
        t1=e1.emb_.emb_
        t2=e1.emb.emb_.emb
        self.assertEqual(e1.__debug_internal__(),  (1, 0, -1, 1), "e1 internal status")
        self.assertEqual(e4.__debug_internal__(),  (1, 1, -1, 2), "e4 internal status")
        self.assertEqual(e5.__debug_internal__(),  (1, 1, -1, 2), "e5 internal status")
        self.assertEqual(t1.__debug_internal__(),  (1, 1, -1, 2), "t1 internal status")
        self.assertEqual(t2.__debug_internal__(),  (1, 1, -1, 2), "t2 internal status")

        # now convert first P3 to a P2 (see how value(r1) is converted from P2 to P3)
        r1=libpython_obj.sample_embed2()
        r2=r1.emb.emb.emb
        self.assertEqual(r1.__debug_internal__(),  (1, 0, -1, 1), "r1 internal status")
        e1.emb=r1
        self.assertEqual(r1.__debug_internal__(),  (1, 1, -1, 2), "r1 internal status after")

        # see how t3 is converted from P3 to P2
        t3=e1.emb_
        self.assertEqual(t3.__debug_internal__(),  (1, 1, -1, 2), "t3 internal status")
        e1.emb_=libpython_obj.sample_embed3()
        self.assertEqual(t3.__debug_internal__(),  (1, 0, -1, 1), "t3 internal status after")

        # now del extracted branch, and everything else
        del e4, e5, t3, t1, t2, r2, r1

    def test_07(self):
        "Assign P3 to P1: error"
        self.cc=libpython_obj.container_class()
        self.p3=self.cc.sample_obj

        # Assign P3 object to reference counted pointer
        self.assertExpression(TypeError, "self.cc.sample_objp=self.p3")
        self.assertEqual(self.cc.sample_objp, None)
        del self.cc, self.p3

    def test_08(self):
        "Assign P3 to P2: error"
        self.cc=libpython_obj.container_class()
        self.p3=self.cc.sample_str_obj

        # Assign P3 object to reference counted pointer
        self.assertExpression(TypeError, "self.cc.sample_str_objp=self.p3")
        self.assertEqual(self.cc.sample_str_objp, None)
        del self.cc, self.p3

    def test_09(self):
        "Returning objects by reference"
        cc=libpython_obj.container_class()
        # TODO: cc.new_sample_str_objp()
        self.assertEqual(cc.refret_struct1(), cc.sample_str_obj)
        self.assertEqual(cc.refret_struct2(), cc.sample_str_objp)

    def test_10(self):
        "Reference counted objects in detail"
        c2=libpython_obj.counted2()
        c1=c2.new_counted1()

    def test_11(self):
        "Assign derived object to base: generalize"
        self.cc=libpython_obj.container_class()
        self.sc=libpython_obj.sample_class()
        self.xc=libpython_obj.specialized_class()
        self.rc=libpython_obj.reference_class()

        # valid generalization
        self.cc.refobj=self.sc
        self.assertEqual(type(self.cc.refobj), libpython_obj._sample_class)
        self.assertEqual(type(self.sc), libpython_obj._sample_class)
        self.assertExpression(TypeError, "self.cc.refobj=4")
        self.assertEqual(self.cc.refobj.virtual_method(16), 32)
        self.assertEqual(self.sc.virtual_method(16), 32)
        self.assertEqual(libpython_obj.reference_class().virtual_method(16), 8)

        self.cc.refobj=self.xc
        self.assertEqual(self.cc.refobj.virtual_method(16), 48)

        self.cc.sample_objp=self.xc
        self.assertEqual(self.cc.sample_objp.virtual_method(16), 48)

        # invalid specialization
        self.assertExpression(TypeError, "self.cc.sample_objp=self.rc")

    def test_12(self):
        "Assign derived object to base: generalize II"
        self.cc=libpython_obj.container_class()
        self.xc=libpython_obj.reference_class()
        self.assertExpression(TypeError, "self.cc.sample_obj=self.xc") # not allowed in contained objs

    def test_13(self):
        "Generalization and serialization"
        import pickle
        cc=libpython_obj.container_class()

        cc.refobj=libpython_obj.specialized_class()
        self.assertEqual(cc.refobj.virtual_method(16), 48)
        cc=pickle.loads(pickle.dumps(cc))
        self.assertEqual(cc.refobj.virtual_method(16), 48)

    def test_14(self):
        "Array of objects"
        import pickle
        obj=libpython_obj.array_container()

        # first check object consistency
        self.assertEqual(obj.get_value(1, 2), 0)
        obj.set_value(1, 2, 17)
        self.assertEqual(obj.get_value(1, 2), 17)
        self.assertEqual(obj.get_value(2, 1), 0)

        # try accessing structure directly
        self.assertEqual(obj.array_object[1].inner_array_object[2].value, 17)
        obj.array_object[1].inner_array_object[2].value=-55
        self.assertEqual(obj.array_object[1].inner_array_object[2].value, -55)

        # types of objects returned (P3)
        self.assertEqual(obj.array_object[0].__debug_internal__(), (1, 1, -1, 2))
        self.assertEqual(obj.array_object[0].inner_array_object[0].__debug_internal__(), (1, 1, -1, 2))

        # serialization
        obj2=pickle.loads(pickle.dumps(obj))

        # assign created objs
        obj.array_object=[libpython_obj.array_container.to_create_array(),
                          libpython_obj.array_container.to_create_array(),
                          libpython_obj.array_container.to_create_array(),
                          libpython_obj.array_container.to_create_array(),
                          libpython_obj.array_container.to_create_array(),
                          libpython_obj.array_container.to_create_array(),
                          libpython_obj.array_container.to_create_array()]

        # more complex modification
        list=obj.array_object
        list.pop()
        newobj=libpython_obj.array_container.to_create_array()
        newobj.x=123
        newobj.y=321
        list2=newobj.inner_array_object
        list2.pop()
        newobj2=libpython_obj.array_container.to_create_array.inner_obj_array()
        newobj2.value=86420
        list2.append(newobj2)
        newobj.inner_array_object=list2
        list.append(newobj)
        obj.array_object=list
        self.assertEqual(obj.array_object[6].x, 123)
        self.assertEqual(obj.array_object[6].y, 321)
        self.assertEqual(obj.array_object[6].inner_array_object[12].value, 86420)

    def test_15(self):
        "Vector of objects"
        obj=libpython_obj.object_vector()
        self.assertEqual(obj.v, [])
        obj.add_v()
        self.assertEqual(len(obj.v), 1)

    def test_16(self):
        "Vector of object pointers"
        obj=libpython_obj.object_vector()
        self.assertEqual(obj.w, [])
        obj.add_w()
        self.assertEqual(len(obj.w), 1)

    def test_17(self):
        "Contained object after pickle - unplickle"
        import pickle
        cc=libpython_obj.container_class()
        cc.new_sample_str_objp()
        ss=cc.sample_str_objp
        del cc

        str=pickle.dumps(ss)
        del ss

        ss=pickle.loads(str)

    def test_18(self):
        "Multiple embedded P4"
        p3=libpython_obj.p4_3()
        p2=p3.p
        p1=p2.p

        self.assertEqual(p1.__debug_internal__(), (1, 1, -1, 3), "p1 is P4")
        self.assertEqual(p2.__debug_internal__(), (1, 1, -1, 3), "p2 is P4")
        self.assertEqual(p3.__debug_internal__(), (1, 0, -1, 1), "p3 is P2")

    def test_19(self):
        "Multiple embedded P4, chain broken"
        p3=libpython_obj.p4_3()
        self.p2=p3.p
        self.p1=self.p2.p
        p3.del_p()

        self.assertExpression(TypeError, "self.p1.x")
        self.assertExpression(TypeError, "self.p2.p")

        

if __name__ == '__main__':

    suite = unittest.TestLoader().loadTestsFromTestCase(test_5_obj)
    result=python_runner.BasicTestRunner(iterations=len(sys.argv)>1 and sys.argv[1] or 1,
	stream=len(sys.argv)>2 and open(sys.argv[2], "w") or sys.stderr).run(suite)
    sys.exit(not result.wasSuccessful())
